﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;

namespace Jemgine.Engine.Scripts
{
    [Jemgine.Engine.Node, Jemgine.Engine.Category("Templated Script")]
    public class TemplateInput : Jemgine.Data.ScriptNode
    {
        public TemplateInput()
        {
            AddInput("In", null);
            AddOutput("Out", null);
        }
    }

    [Jemgine.Engine.Node, Jemgine.Engine.Category("Templated Script")]
    public class TemplateOutput : Jemgine.Data.ScriptNode
    {
        public TemplateOutput()
        {
            AddInput("In", null);
            AddOutput("Out", null);
        }
    }

    [Jemgine.Engine.Node, Jemgine.Engine.Category("Templated Script")]
    public class TemplateReference : Jemgine.Data.ScriptNode
    {
    }

    public class InitializeTemplatedScript : Jemgine.Engine.EditorInitializationAttribute
    {
        public override void Initialize_NewItem(Object Element, Jemgine.Engine.EditorStateInterface ESI)
        {
            var Node = Element as TemplatedScript;
            if (Node == null) throw new InvalidProgramException();
            Node.Clear();
            Initialize_EditorLoad(Element, ESI);
        }

        public override void Initialize_EditorLoad(Object Element, Jemgine.Engine.EditorStateInterface ESI)
        {
#if !XBOX
            var Node = Element as TemplatedScript;
            if (Node == null) throw new InvalidProgramException();

            try
            {
                if (String.IsNullOrEmpty(Node.TemplateName)) return;
                Node.Template = ESI.ReadMap(System.IO.Path.Combine(ESI.WorkingDirectory(), Node.TemplateName + ".map"));
                Node.EditorInitialize();
                
            }
            catch (Exception) { }
#endif
        }

        public override void OnPropertyChange(object Element, Jemgine.Engine.EditorStateInterface ESI)
        {
            Initialize_NewItem(Element, ESI);
        }

    }


    [Jemgine.Engine.Node, Jemgine.Engine.Category("Templated Script"), InitializeTemplatedScript]
    public class TemplatedScript : Jemgine.Data.ScriptNode, Jemgine.Data.MapElement
    {
        public string TemplateName { get; set; }
        public int Inputs { get; set; }

        internal Data.MapData Template = null;

        internal class MustOnlyContainNodes : Exception { }

        private List<TemplateInput> TemplateInputs = new List<TemplateInput>();
        private List<TemplateOutput> TemplateOutputs = new List<TemplateOutput>();
        private List<TemplateReference> TemplateReferences = new List<TemplateReference>();

        internal void Clear()
        {
            TemplateInputs.Clear();
            TemplateOutputs.Clear();
            TemplateReferences.Clear();

            base.ClearOutputs();
            base.ClearReferences();
            base.ClearInputs();

            Template = null;
            Inputs = 0;
        }

        internal void EditorInitialize()
        {
            foreach (var Element in Template.Elements) ElementHandler(Element);

            InputTerminalInfo.Clear();
            OutputTerminalInfo.Clear();
            ReferenceTerminalInfo.Clear();

            for (int i = 0; i < TemplateInputs.Count; ++i)
            {
                if (i >= InputTerminals.Count)
                    InputTerminals.Add(new Data.InputTerminal { Me = this, LocalID = InputTerminals.Count });
                InputTerminalInfo.Add(new Data.TerminalInfo(TemplateInputs[i].Name, null));
            }

            for (int i = 0; i < TemplateOutputs.Count; ++i)
            {
                if (i >= OutputTerminals.Count)
                    OutputTerminals.Add(new Data.OutputTerminal());
                OutputTerminalInfo.Add(new Data.TerminalInfo(TemplateOutputs[i].Name, null));
            }

            for (int i = 0; i < TemplateReferences.Count; ++i)
            {
                if (i >= ReferenceTerminals.Count)
                    ReferenceTerminals.Add(new Data.ReferenceTerminal());
                ReferenceTerminalInfo.Add(new Data.TerminalInfo(TemplateReferences[i].Name, null));
            }

            while (TemplateInputs.Count < InputTerminals.Count)
            {
                InputTerminals[InputTerminals.Count - 1].ID = -1;
                InputTerminals.RemoveAt(InputTerminals.Count - 1);
            }

            while (TemplateOutputs.Count < OutputTerminals.Count)
                OutputTerminals.RemoveAt(OutputTerminals.Count - 1);

            while (TemplateReferences.Count < ReferenceTerminals.Count)
                ReferenceTerminals.RemoveAt(ReferenceTerminals.Count - 1);

            Inputs = this.TemplateInputs.Count;
            Template = null;
        }

        private void ElementHandler(Data.MapElement Element)
        {
            if (!(Element is Data.ScriptNode)) throw new MustOnlyContainNodes();
            if (Element is TemplateInput) TemplateInputs.Add(Element as TemplateInput);
            if (Element is TemplateOutput) TemplateOutputs.Add(Element as TemplateOutput);
            if (Element is TemplateReference) TemplateReferences.Add(Element as TemplateReference);
        }

        new public void EnumerateTargets(List<Data.InputTerminal> Inputs, List<Data.ReferenceTarget> Targets)
        {
            for (int I = 0; I < this.Inputs; ++I)
                AddInput("", null);
            base.EnumerateTargets(Inputs, Targets);
        }

        new public void PostProcess(Vector2 Offset, Data.MapData Map, Engine.Simulation Sim)
        {
            base.PostProcess(Offset, Map, Sim);

            if (Sim != null)
            {
                try
                {
                    Template = new Data.MapData(Sim.EpisodeContent.Load<Data.MapData>(TemplateName));
                }
                catch (ContentLoadException) { Template = null; return; }

                try
                {
                    Template.PostProcess(Sim, Position, ElementHandler);
                }
                catch (MustOnlyContainNodes) { Template = null; return; }


                if (TemplateOutputs.Count != OutputTerminals.Count) return;
                if (TemplateReferences.Count != ReferenceTerminals.Count) return;

                foreach (var Element in Template.Elements)
                {
                    var Node = Element as Data.ScriptNode;

                    foreach (var Terminal in Node.ReferenceTerminals)
                        for (int I = 0; I < TemplateReferences.Count; ++I)
                            if (Terminal.Target == TemplateReferences[I]) Terminal.Target = this.ReferenceTerminals[I].Target;

                    foreach (var Terminal in Node.OutputTerminals)
                        for (int I = 0; I < TemplateOutputs.Count; ++I)
                            if (Terminal.Target == TemplateOutputs[I].InputTerminals[0])
                                Terminal.Target = this.OutputTerminals[I].Target;
                }
            }
        }

        public override void FireTerminal(int InputID, Jemgine.Data.ScriptPayload Payload)
        {
            if (Template == null) return;
            if (InputID >= TemplateInputs.Count) return;
            TemplateInputs[InputID].OutputTerminals[0].Fire(Payload);
        }
    }
}
